<script setup lang="ts">
import { onBeforeUnmount, onMounted, nextTick, ref, watch } from 'vue';

const props: any = defineProps<{
    ujt?: any;
    asaidata: any;
    formdata: any;
    formkey: any;
}>();
const { $global } = props.ujt;

const richConfig: any = {
    img: {
        maxWidth: 288,
        quality: 0.8,
    },
    imgurls: {},
};
const fileInput: any = ref(null);
const rfRichEditor: any = ref(null);
const rfRichCode: any = ref(null);
const rfRichType = ref(props.asaidata?.rule?.[props.formkey]?.editorty || 2);

onMounted(initEditorValue);
function initEditorValue() {
    rfRichEditor.value &&
        (rfRichEditor.value.innerHTML = props.formdata[props.formkey]);
}

// 清理 HTML
function sanitize(html: string, type = 0) {
    const allowedTags = new Set([
        'a',
        'figure',
        'figure',
        'img',
        'video',
        'ul',
        'ol',
        'li',
        'h1',
        'h2',
        'h3',
        'h4',
        'h5',
        'h6',
        'p',
        'strong',
        'em',
        'span',
        'font',
        'b',
        'u',
        'i',
        'strike',
        'nav',
        'footer',
        'section',
        'article',
        'div',
    ]);
    const allowedAttrs = new Set([
        'href',
        'src',
        'alt',
        'size',
        'color',
        'title',
        'style',
        'controls',
        'download',
    ]);
    const doc = new DOMParser().parseFromString(html, 'text/html');

    // 反向遍历避免DOM变动影响遍历
    Array.from(doc.body.querySelectorAll('*'))
        .reverse()
        .forEach((el: any) => {
            const tag = el.tagName.toLowerCase();
            if (!allowedTags.has(tag)) {
                return el.remove();
            }
            if (
                type &&
                tag == 'img' &&
                el?.src &&
                !richConfig.imgurls?.[el.src]
            ) {
                el.setAttribute('crossOrigin', 'anonymous');
                richConfig.imgurls[el.src] = '1';
            }
            Array.from(el.attributes).forEach(({ name, value }: any) => {
                if (!allowedAttrs.has(name)) {
                    return el.removeAttribute(name);
                }
                if (['href', 'src'].includes(name)) {
                    const clean = sanitizeURL(value, type);
                    clean
                        ? el.setAttribute(name, clean)
                        : el.removeAttribute(name);
                }
            });
        });
    return doc.body.innerHTML;
}

function sanitizeURL(url: string, type = 0) {
    try {
        if (type) {
            return url;
        }
        const parsed = new URL(url, location.href);
        if (
            [
                'http:',
                'https:',
                'mailto:',
                'tel:',
                'blob:',
                'data:',
                'ftp:',
            ].includes(parsed.protocol)
        ) {
            return parsed.href;
        }
    } catch {
        return url.startsWith('#') || !/:/.test(url) ? url : '';
    }
    return '';
}
// 通过系统弹框添加链接
function addLinkByPrompt() {
    // 弹出系统输入框，提示用户输入链接地址
    const url: any = window.prompt('请输入链接地址');
    if (url) {
        // 调用 execCommand 函数创建链接
        execCommand('createLink', url);
    }
}
// 文件处理
function showMediaDialog(type: any) {
    fileInput.value.accept = `${type}/*`;
    nextTick(() => fileInput.value?.click());
}
async function handleFileUpload(e: any) {
    const file = e.target.files?.[0];
    if (!file) {
        return;
    }
    let url: any;
    let html: any = '';
    if (file.type.startsWith('image')) {
        url = await compressFile(file);
        html = `<img src="${url}" style="max-width:100%">`;
    } else {
        if (file.size > 1024 * 1024) {
            url = URL.createObjectURL(file);
        } else {
            url = await compressFile(file);
        }
        if (file.type.startsWith('video')) {
            html = `<video controls src="${url}" style="max-width:100%"></video>`;
        } else {
            html = `<a href="${url}" download="${file.name}">${file.name}</a>`;
        }
    }
    execCommand('insertHTML', html);
}
function compressFile(file: any) {
    return new Promise((resolve, reject) => {
        if (!file) {
            return reject(new Error('Invalid file'));
        }
        const reader = new FileReader();
        reader.onload = (e: any) => {
            if (file.type.startsWith('image/')) {
                // 处理图片文件
                compressImage(e.target.result)
                    .then((res) => {
                        resolve(res);
                    })
                    .catch((err) => {
                        reject(err);
                    });
            } else {
                // 处理视频和其他文件
                resolve(e.target.result);
            }
        };
        reader.onerror = () => reject(new Error('File read failed'));
        reader.readAsDataURL(file);
    });
}
function compressImage(url: any) {
    return new Promise((resolve, reject) => {
        const img = new Image();
        const canvas = document.createElement('canvas');
        const ctx: any = canvas.getContext('2d', {
            willReadFrequently: false,
        });
        // 性能优化：释放资源标识
        let resourcesCleaned = false;
        const cleanupResources = () => {
            if (resourcesCleaned) return;
            img.onload = img.onerror = null;
            img.src = '';
            canvas.width = canvas.height = 0;
            resourcesCleaned = true;
        };
        img.crossOrigin = 'anonymous'; // 先设置图片跨域属性
        img.src = url;
        img.onload = () => {
            try {
                // 计算缩放比例
                const scale = Math.min(richConfig.img.maxWidth / img.width, 1);
                canvas.width = scale < 1 ? richConfig.img.maxWidth : img.width;
                canvas.height = scale < 1 ? img.height * scale : img.height;

                // 高性能绘制设置
                ctx.imageSmoothingEnabled = true;
                ctx.imageSmoothingQuality = 'high';

                ctx.clearRect(0, 0, canvas.width, canvas.height);
                ctx.drawImage(img, 0, 0, canvas.width, canvas.height);

                // 转换为JPEG
                const dataUrl = canvas.toDataURL(
                    'image/jpeg',
                    richConfig.img.quality
                );
                cleanupResources();
                resolve(dataUrl);
            } catch (error) {
                cleanupResources();
                reject(error);
            }
        };

        img.onerror = (err) => {
            cleanupResources();
            reject(new Error('Image load failed'));
        };
    });
}

function onUploadImg(e: any) {
    e.preventDefault();
    Object.keys(richConfig.imgurls).forEach((key) => {
        compressImage(richConfig.imgurls[key]).then((dataUrl) => {
            if (richConfig.imgurls[key] == '1') {
                richConfig.imgurls[key] = '2';
                props.formdata[props.formkey].replaceAll(
                    richConfig.imgurls[key],
                    dataUrl
                );
            }
        });
    });
}
function onPaste(e: any) {
    e.preventDefault();
    const html = e.clipboardData?.getData('text/html') || '';
    const clean =
        sanitize(html, 1) || e.clipboardData?.getData('text/plain') || '';
    execCommand('insertHTML', clean);
}
function onEditor(e: any) {
    rfRichEditor.value &&
        (props.formdata[props.formkey] = sanitize(
            rfRichEditor.value.innerHTML
        ));
}

function execCommand(command: any, value: any = null) {
    if (!rfRichEditor.value) {
        return;
    }
    document.execCommand(command, false, value);
}
function changeView() {
    rfRichType.value++;
    if (rfRichType.value > 2) {
        rfRichType.value = 0;
    }
    nextTick(initEditorValue);
}
</script>

<template>
    <div
        class="as-richtext-area"
        :style="props.asaidata.rule?.[props.formkey]?.style || {}"
    >
        <div class="as-richtext-toolbar">
            <select @change="execCommand('fontSize', $event.target.value)">
                <option value="">
                    {{ $global.lang.richtexteditor.fontsize }}
                </option>
                <option
                    v-for="i in 8"
                    :key="'size' + i"
                    :value="i * (i - 1) + 8"
                >
                    {{ i * (i - 1) + 8 }}px
                </option>
            </select>
            <select @change="execCommand('formatBlock', $event.target.value)">
                <option value="p">
                    {{ $global.lang.richtexteditor.paragraph }}
                </option>
                <option v-for="i in 6" :key="'h' + i" :value="'h' + i">
                    {{ $global.lang.richtexteditor.title }}{{ i }}
                </option>
            </select>
            <button
                @pointerdown="execCommand('bold')"
                :title="$global.lang.richtexteditor.bold"
            >
                B
            </button>
            <button
                @pointerdown="execCommand('underline')"
                :title="$global.lang.richtexteditor.underline"
            >
                U
            </button>
            <button
                @pointerdown="execCommand('italic')"
                :title="$global.lang.richtexteditor.italic"
            >
                I
            </button>
            <button
                @pointerdown="execCommand('strikethrough')"
                :title="$global.lang.richtexteditor.strikethrough"
            >
                S
            </button>
            <input
                type="color"
                @input="execCommand('foreColor', $event.target.value)"
                :title="$global.lang.richtexteditor.foreColor"
            />
            <input
                type="color"
                @input="execCommand('backColor', $event.target.value)"
                :title="$global.lang.richtexteditor.backColor"
            />
            <button
                @pointerdown="execCommand('insertOrderedList')"
                :title="$global.lang.richtexteditor.insertOrderedList"
            >
                有
            </button>
            <button
                @pointerdown="execCommand('insertUnorderedList')"
                :title="$global.lang.richtexteditor.insertUnorderedList"
            >
                无
            </button>
            <button @pointerdown="execCommand('justifyCenter')" title="居中">
                中
            </button>
            <button @pointerdown="execCommand('justifyFull')" title="两端对齐">
                两
            </button>
            <button @pointerdown="execCommand('justifyRight')" title="右对齐">
                右
            </button>
            <button @pointerdown="execCommand('justifyLeft')" title="左对齐">
                左
            </button>
            <button @pointerdown="execCommand('delete')" title="删除">
                删
            </button>
            <button @pointerdown="addLinkByPrompt()" title="链接">链</button>
            <button @pointerdown="execCommand('unlink')" title="删除链接">
                除
            </button>

            <button @pointerdown="showMediaDialog('image')" title="图片">🖼️</button>
            <button @pointerdown="showMediaDialog('video')" title="视频">🎬</button>
            <button @pointerdown="showMediaDialog('file')" title="文件">📎</button>
            <button @pointerdown="onUploadImg" title="上传">上</button>
            <button @pointerdown="changeView()" title="切换">HTML</button>
        </div>
        <div class="as-richtext-content">
            <div
                v-if="rfRichType != 1"
                :style="{ width: rfRichType > 1 ? '50%' : '100%' }"
                ref="rfRichEditor"
                contenteditable="true"
                @paste="onPaste"
                @input="onEditor"
            ></div>
            <textarea
                :placeholder="props?.opt?.placeholder || ''"
                ref="rfRichCode"
                v-model="props.formdata[props.formkey]"
                v-if="rfRichType > 0"
                :style="{ width: rfRichType > 1 ? '50%' : '100%' }"
            ></textarea>
        </div>
        <input ref="fileInput" type="file" hidden @change="handleFileUpload" />
    </div>
</template>

<style scoped>
.as-richtext-area {
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.as-richtext-toolbar {
    height: auto;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
}

.as-richtext-toolbar > select,
.as-richtext-toolbar > button,
.as-richtext-toolbar > input {
    outline: 0;
    border: 0;
    width: 38px;
    height: 38px;
    overflow: hidden;
    display: inline-flex;
    justify-content: center;
    align-items: center;
    cursor: pointer;
    font-size: 14px;
    background-color: var(--ch2);
    cursor: pointer;
    &:hover {
        background-color: var(--ch4);
    }
}
.as-richtext-toolbar > select {
    width: 68px;
}

.as-richtext-content {
    flex-grow: 1;
    overflow: hidden;
    display: flex;
}
.as-richtext-content > div,
.as-richtext-content > textarea {
    margin: 0;
    padding: 0;
    outline: 0;
    border: 0;
    resize: none;
    height: 100%;
    overflow: auto;
}
</style>
